// SPDX-License-Identifier: GPL-2.0-only
/*
 * Example code for CSV RTMR & ATTESTATION.
 *
 * Copyright (C) 2025 Hygon Info Technologies Ltd.
 *
 * Author: Liyang Han <hanliyang@hygon.cn>
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include <getopt.h>
#include <sys/ioctl.h>
#include <openssl/hmac.h>

#include "csv-guest-module-tool.h"
#include "rtmr.h"
#include "attestation.h"
#include "hygoncert.h"

static void _restore_legacy_report(csv_attestation_report_t *report)
{
	csv_attestation_tee_info_t *tee_info = &report->tee_info;
	uint32_t *ptr = NULL;
	int i;

	// user_pubkey_digest
	ptr = (uint32_t *)tee_info->user_pubkey_digest;
	for (i = 0; i < sizeof(tee_info->user_pubkey_digest) / sizeof(uint32_t); i++)
		ptr[i] ^= tee_info->anonce;
	// vm_id
	ptr = (uint32_t *)tee_info->vm_id;
	for (i = 0; i < sizeof(tee_info->vm_id) / sizeof(uint32_t); i++)
		ptr[i] ^= tee_info->anonce;
	// vm_version
	ptr = (uint32_t *)tee_info->vm_version;
	for (i = 0; i < sizeof(tee_info->vm_version) / sizeof(uint32_t); i++)
		ptr[i] ^= tee_info->anonce;
	// user_data
	ptr = (uint32_t *)tee_info->user_data;
	for (i = 0; i < sizeof(tee_info->user_data) / sizeof(uint32_t); i++)
		ptr[i] ^= tee_info->anonce;
	// mnonce
	ptr = (uint32_t *)tee_info->mnonce;
	for (i = 0; i < sizeof(tee_info->mnonce) / sizeof(uint32_t); i++)
		ptr[i] ^= tee_info->anonce;
	// measure
	ptr = (uint32_t *)tee_info->measure;
	for (i = 0; i < sizeof(tee_info->measure) / sizeof(uint32_t); i++)
		ptr[i] ^= tee_info->anonce;
	// policy
	tee_info->policy ^= tee_info->anonce;
	// sig_usage
	tee_info->sig_usage ^= tee_info->anonce;
	// sig_algo
	tee_info->sig_algo ^= tee_info->anonce;

	// pek_cert
	ptr = (uint32_t *)report->pek_cert;
	for (i = 0; i < sizeof(report->pek_cert) / sizeof(uint32_t); i++)
		ptr[i] ^= tee_info->anonce;
	// chip_id
	ptr = (uint32_t *)report->chip_id;
	for (i = 0; i < sizeof(report->chip_id) / sizeof(uint32_t); i++)
		ptr[i] ^= tee_info->anonce;
}

static int do_attestation_parse(void)
{
	int fd;
	int ret = 0;
	int i;
	uint8_t *ptr = NULL;
	uint8_t *buffer = NULL;
	csv_attestation_report_t *report = NULL;
	csv_attestation_tee_info_t *tee_info = NULL;

	// allocate 4K size buffer to load report
	buffer = calloc(1, PAGE_SIZE);
	if (!buffer) {
		PR_ERR("allocate 0x%llx bytes buffer fail\n", PAGE_SIZE);
		return -1;
	}

	fd = open(CSV_ATTESTATION_REPORT_OUT_FILE, O_RDONLY);
	if (fd < 0) {
		PR_ERR("cannot open file %s\n", CSV_ATTESTATION_REPORT_OUT_FILE);
		ret = -1;
		goto err_free_buffer;
	}

	if (read(fd, buffer, CSV_ATTESTATION_REPORT_SIZE)
	    != CSV_ATTESTATION_REPORT_SIZE) {
		PR_ERR("read report form %s fail\n", CSV_ATTESTATION_REPORT_OUT_FILE);
		ret = -1;
		goto err_close_fd;
	}

	report = (csv_attestation_report_t *)buffer;
	tee_info = &report->tee_info;

	// Restore original raw data which XORed by anonce
	//_restore_legacy_report(report);

	PR_INFO("Parsed Legacy Attestation Report:\n");
	PR_INFO("  TEE_INFO:\n");
	PR_INFO("    user_pubkey_digest: ");
	ptr = (uint8_t *)tee_info->user_pubkey_digest;
	for (i = 0; i < sizeof(sm3_hash_block_t); i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    vm_id: ");
	ptr = (uint8_t *)tee_info->vm_id;
	for (i = 0; i < VM_ID_SIZE; i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    vm_version: ");
	ptr = (uint8_t *)tee_info->vm_version;
	for (i = 0; i < VM_VERSION_SIZE; i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    user_data: ");
	ptr = (uint8_t *)tee_info->user_data;
	for (i = 0; i < REPORT_USER_DATA_SIZE; i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    mnonce: ");
	ptr = (uint8_t *)tee_info->mnonce;
	for (i = 0; i < REPORT_MNONCE_SIZE; i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    measure: ");
	ptr = (uint8_t *)tee_info->measure;
	for (i = 0; i < sizeof(sm3_hash_block_t); i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    policy: ");
	ptr = (uint8_t *)&tee_info->policy;
	for (i = 0; i < sizeof(uint32_t); i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    sig_usage: ");
	ptr = (uint8_t *)&tee_info->sig_usage;
	for (i = 0; i < sizeof(uint32_t); i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    sig_algo: ");
	ptr = (uint8_t *)&tee_info->sig_algo;
	for (i = 0; i < sizeof(uint32_t); i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    anonce: ");
	ptr = (uint8_t *)&tee_info->anonce;
	for (i = 0; i < sizeof(tee_info->anonce); i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    sig1: ");
	ptr = (uint8_t *)tee_info->sig1;
	for (i = 0; i < sizeof(tee_info->sig1); i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("  PEK Cert: ");
	ptr = (uint8_t *)report->pek_cert;
	for (i = 0; i < PEK_CERT_SIZE; i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("  Chip ID: ");
	ptr = (uint8_t *)report->chip_id;
	for (i = 0; i < SN_SIZE; i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("  Sealing Key: ");
	ptr = (uint8_t *)report->sealing_key;
	for (i = 0; i < SEALING_KEY_SIZE; i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("  sm3 hmac: ");
	ptr = (uint8_t *)report->sm3_hmac;
	for (i = 0; i < sizeof(sm3_hash_block_t); i++)
		printf("%02x", ptr[i]);
	printf("\n");

err_close_fd:
	close(fd);
err_free_buffer:
	free(buffer);

	return ret;
}

static int do_attestation_parse_ext(uint32_t flags)
{
	int fd;
	int ret = 0;
	int i;
	uint8_t *ptr = NULL;
	uint8_t *buffer = NULL;
	csv3_attestation_report_ext_t *report_ext = NULL;
	csv3_attestation_tee_info_ext_t *tee_info_ext = NULL;

	if (flags && !(flags & CSV_ATTESTATION_FLAG_REPORT_EXT)) {
		PR_ERR("unknown extension flags 0x%x.\n", flags);
		return -1;
	}

	if (!flags) {
		PR_INFO("extension flags are 0, we will parse the report in legacy format.\n");
		return do_attestation_parse();
	}

	// allocate 4K size buffer to load report
	buffer = calloc(1, PAGE_SIZE);
	if (!buffer) {
		PR_ERR("allocate 0x%llx bytes buffer fail\n", PAGE_SIZE);
		return -1;
	}

	fd = open(CSV_ATTESTATION_REPORT_OUT_FILE, O_RDONLY);
	if (fd < 0) {
		PR_ERR("cannot open file %s\n", CSV_ATTESTATION_REPORT_OUT_FILE);
		ret = -1;
		goto err_free_buffer;
	}

	if (read(fd, buffer, CSV3_ATTESTATION_REPORT_EXT_SIZE)
	    != CSV3_ATTESTATION_REPORT_EXT_SIZE) {
		PR_ERR("read report form %s fail\n", CSV_ATTESTATION_REPORT_OUT_FILE);
		ret = -1;
		goto err_close_fd;
	}

	report_ext = (csv3_attestation_report_ext_t *)buffer;
	tee_info_ext = &report_ext->tee_info;

	PR_INFO("Parsed Extended Attestation Report:\n");
	PR_INFO("  TEE_INFO:\n");
	PR_INFO("    user_pubkey_digest: ");
	ptr = (uint8_t *)tee_info_ext->user_pubkey_digest;
	for (i = 0; i < sizeof(sm3_hash_block_t); i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    vm_id: ");
	ptr = (uint8_t *)tee_info_ext->vm_id;
	for (i = 0; i < VM_ID_SIZE; i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    vm_version: ");
	ptr = (uint8_t *)tee_info_ext->vm_version;
	for (i = 0; i < VM_VERSION_SIZE; i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    user_data: ");
	ptr = (uint8_t *)tee_info_ext->user_data;
	for (i = 0; i < REPORT_USER_DATA_SIZE; i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    mnonce: ");
	ptr = (uint8_t *)tee_info_ext->mnonce;
	for (i = 0; i < REPORT_MNONCE_SIZE; i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    measure: ");
	ptr = (uint8_t *)tee_info_ext->measure;
	for (i = 0; i < sizeof(sm3_hash_block_t); i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    policy: ");
	ptr = (uint8_t *)&tee_info_ext->policy;
	for (i = 0; i < sizeof(uint32_t); i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    sig_usage: ");
	ptr = (uint8_t *)&tee_info_ext->sig_usage;
	for (i = 0; i < sizeof(uint32_t); i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    sig_algo: ");
	ptr = (uint8_t *)&tee_info_ext->sig_algo;
	for (i = 0; i < sizeof(uint32_t); i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    build: ");
	ptr = (uint8_t *)&tee_info_ext->build;
	for (i = 0; i < sizeof(uint32_t); i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    rtmr_version: ");
	ptr = (uint8_t *)&tee_info_ext->rtmr_version;
	for (i = 0; i < sizeof(uint32_t); i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    reserved0: ");
	ptr = (uint8_t *)tee_info_ext->reserved0;
	for (i = 0; i < sizeof(tee_info_ext->reserved0); i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    rtmr: ");
	ptr = (uint8_t *)tee_info_ext->rtmr;
	for (i = 0; i < sizeof(tee_info_ext->rtmr); i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    reserved1: ");
	ptr = (uint8_t *)tee_info_ext->reserved1;
	for (i = 0; i < sizeof(tee_info_ext->reserved1); i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("    sig1: ");
	ptr = (uint8_t *)tee_info_ext->sig1;
	for (i = 0; i < sizeof(tee_info_ext->sig1); i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("  PEK Cert: ");
	ptr = (uint8_t *)report_ext->pek_cert;
	for (i = 0; i < PEK_CERT_SIZE; i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("  Chip ID: ");
	ptr = (uint8_t *)report_ext->chip_id;
	for (i = 0; i < SN_SIZE; i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("  Sealing Key: ");
	ptr = (uint8_t *)report_ext->sealing_key;
	for (i = 0; i < SEALING_KEY_SIZE; i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("  sm3 hmac: ");
	ptr = (uint8_t *)report_ext->sm3_hmac;
	for (i = 0; i < sizeof(sm3_hash_block_t); i++)
		printf("%02x", ptr[i]);
	printf("\n");
	PR_INFO("  reserved: ");
	ptr = (uint8_t *)report_ext->reserved;
	for (i = 0; i < sizeof(report_ext->reserved); i++)
		printf("%02x", ptr[i]);
	printf("\n");

err_close_fd:
	close(fd);
err_free_buffer:
	free(buffer);

	return ret;
}

static int do_attestation_report(void)
{
	int fd;
	int report_fd;
	int ret = 0;
	uint8_t *buffer = NULL;
	csv_guest_user_data_attestation_t *udata = NULL;
	struct csv_report_req req = { 0, };

	EVP_MD_CTX *mdctx = NULL;
	unsigned int digest_len = sizeof(sm3_hash_block_t);

	fd = open_csv_guest();
	if (fd < 0) {
		return -1;
	}

	// always allocate 4K size buffer
	buffer = calloc(1, PAGE_SIZE);
	if (!buffer) {
		PR_ERR("allocate 0x%llx bytes buffer fail\n", PAGE_SIZE);
		ret = -1;
		goto err_close_fd;
	}

	// populate @user_data, @mnonce, and sm3 hash of them
	udata = (csv_guest_user_data_attestation_t *)buffer;
	ret = gen_random_data((void *)udata->user_data, REPORT_USER_DATA_SIZE);
	if (ret) {
		PR_ERR("generate @user_data fail\n");
		goto err_free_buffer;
	}
	ret = gen_random_data((void *)udata->mnonce, REPORT_MNONCE_SIZE);
	if (ret) {
		PR_ERR("generate @mnonce fail\n");
		goto err_free_buffer;
	}
	mdctx = EVP_MD_CTX_new();
	EVP_DigestInit_ex(mdctx, EVP_sm3(), NULL);
	EVP_DigestUpdate(mdctx, (void *)udata->user_data,
			 REPORT_USER_DATA_SIZE + REPORT_MNONCE_SIZE);
	EVP_DigestFinal_ex(mdctx, udata->hash, &digest_len);
	EVP_MD_CTX_free(mdctx);
	assert(digest_len == sizeof(sm3_hash_block_t));

	PR_INFO("Legacy udata:\n");
	PR_INFO("  user_data: ");
	dump_bin(udata->user_data, REPORT_USER_DATA_SIZE);
	PR_INFO("  mnonce: ");
	dump_bin(udata->mnonce, REPORT_MNONCE_SIZE);
	PR_INFO("  hash: ");
	dump_bin(udata->hash, sizeof(sm3_hash_block_t));

	// issue ATTESTATION request
	req.report_address = (__u64)udata;
	req.len = PAGE_SIZE;
	ret = ioctl(fd, CSV_CMD_GET_REPORT, &req);

	// check return code
	if (ret) {
		PR_ERR("request legacy attestation report fail\n");
		goto err_free_buffer;
	}

	// save attestation report
	report_fd = open(CSV_ATTESTATION_REPORT_OUT_FILE, O_RDWR | O_CREAT, 0644);
	if (report_fd < 0) {
		PR_ERR("cannot open file %s\n", CSV_ATTESTATION_REPORT_OUT_FILE);
		ret = -1;
		goto err_free_buffer;
	}

	if (write(report_fd, buffer, CSV_ATTESTATION_REPORT_SIZE)
	    != CSV_ATTESTATION_REPORT_SIZE) {
		PR_ERR("write report to %s fail\n", CSV_ATTESTATION_REPORT_OUT_FILE);
		ret = -1;
		goto err_close_report_fd;
	}

err_close_report_fd:
	close(report_fd);
err_free_buffer:
	if (buffer)
		free(buffer);
err_close_fd:
	close(fd);

	return ret;
}

static int do_attestation_report_ext(uint32_t flags)
{
	int fd;
	int report_fd;
	int ret = 0;
	uint8_t *buffer = NULL;
	csv_guest_user_data_attestation_ext_t *udata = NULL;
	struct csv_report_req req = { 0, };

	EVP_MD_CTX *mdctx = NULL;
	unsigned int digest_len = sizeof(sm3_hash_block_t);

	uint32_t write_total_size = 0;

	if (flags && !(flags & CSV_ATTESTATION_FLAG_REPORT_EXT)) {
		PR_ERR("unknown extension flags 0x%x.\n", flags);
		return -1;
	}

	fd = open_csv_guest();
	if (fd < 0) {
		return -1;
	}

	// always allocate 4K size buffer
	buffer = calloc(1, PAGE_SIZE);
	if (!buffer) {
		PR_ERR("allocate 0x%llx bytes buffer fail\n", PAGE_SIZE);
		ret = -1;
		goto err_close_fd;
	}

	// populate @user_data, @mnonce, and sm3 hash of them
	udata = (csv_guest_user_data_attestation_ext_t *)buffer;
	ret = gen_random_data((void *)udata->user_data, REPORT_USER_DATA_SIZE);
	if (ret) {
		PR_ERR("generate @user_data fail\n");
		goto err_free_buffer;
	}
	ret = gen_random_data((void *)udata->mnonce, REPORT_MNONCE_SIZE);
	if (ret) {
		PR_ERR("generate @mnonce fail\n");
		goto err_free_buffer;
	}
	mdctx = EVP_MD_CTX_new();
	EVP_DigestInit_ex(mdctx, EVP_sm3(), NULL);
	EVP_DigestUpdate(mdctx, (void *)udata->user_data,
			 REPORT_USER_DATA_SIZE + REPORT_MNONCE_SIZE);
	EVP_DigestFinal_ex(mdctx, udata->hash, &digest_len);
	EVP_MD_CTX_free(mdctx);
	assert(digest_len == sizeof(sm3_hash_block_t));

	// populate @magic and @flags
	memcpy(udata->magic, CSV_ATTESTATION_MAGIC_STRING, sizeof(CSV_ATTESTATION_MAGIC_STRING));
	udata->flags = flags;

	PR_INFO("Extension aware udata:\n");
	PR_INFO("  user_data: ");
	dump_bin(udata->user_data, REPORT_USER_DATA_SIZE);
	PR_INFO("  mnonce: ");
	dump_bin(udata->mnonce, REPORT_MNONCE_SIZE);
	PR_INFO("  hash: ");
	dump_bin(udata->hash, sizeof(sm3_hash_block_t));
	PR_INFO("  magic: %s\n", (char *)udata->magic);
	PR_INFO("  flags: %x\n", udata->flags);

	// issue ATTESTATION request
	req.report_address = (__u64)udata;
	req.len = PAGE_SIZE;
	ret = ioctl(fd, CSV_CMD_GET_REPORT, &req);

	// check return code
	if (ret) {
		PR_ERR("request attestation report (extension aware) fail\n");
		goto err_free_buffer;
	}

	// save attestation report
	report_fd = open(CSV_ATTESTATION_REPORT_OUT_FILE, O_RDWR | O_CREAT, 0644);
	if (report_fd < 0) {
		PR_ERR("cannot open file %s\n", CSV_ATTESTATION_REPORT_OUT_FILE);
		ret = -1;
		goto err_free_buffer;
	}

	if (flags && CSV_ATTESTATION_FLAG_REPORT_EXT) {
		PR_INFO("this extension aware request is for extended attestation report\n");
		write_total_size = CSV3_ATTESTATION_REPORT_EXT_SIZE;
	} else {
		PR_INFO("this extension aware request is for legacy attestation report\n");
		write_total_size = CSV_ATTESTATION_REPORT_SIZE;
	}

	if (write(report_fd, buffer, write_total_size) != write_total_size) {
		PR_ERR("write report to %s fail\n", CSV_ATTESTATION_REPORT_OUT_FILE);
		ret = -1;
		goto err_close_report_fd;
	}

err_close_report_fd:
	close(report_fd);
err_free_buffer:
	if (buffer)
		free(buffer);
err_close_fd:
	close(fd);

	return ret;
}

static int _calc_sm3_hmac(uint8_t *key, uint32_t key_len,
			  uint8_t *data, uint32_t data_len,
			  uint8_t *hmac, uint32_t hmac_len)
{
	unsigned char *result = NULL;
	uint32_t sm3_hmac_out_len = 0;
	int ret = 0;

	result = HMAC(EVP_sm3(), key, key_len, data, data_len, hmac, &sm3_hmac_out_len);
	if (result == NULL) {
		PR_ERR("calc hmac fail\n");
		ret = -1;
		goto err;
	}
	if (sm3_hmac_out_len != hmac_len) {
		PR_ERR("hmac result size invalid\n");
		ret = -1;
	}

err:
	return ret;
}

static int do_attestation_verify(bool verify_certchain)
{
	int fd;
	int ret = 0;
	uint8_t *buffer = NULL;
	uint8_t *buffer2 = NULL;
	csv_attestation_report_t *report = NULL;
	csv_attestation_tee_info_t *tee_info = NULL;
	hygon_csv_cert_t *pek_cert = NULL;

	uint8_t mnonce_tmp[REPORT_MNONCE_SIZE] = { 0, };
	uint32_t *ptr = NULL;
	int i;

	// allocate 4K size buffer to save report
	buffer = calloc(1, PAGE_SIZE);
	if (!buffer) {
		PR_ERR("allocate 0x%llx bytes buffer fail\n", PAGE_SIZE);
		return -1;
	}

	// allocate 4K size buffer to save temporary data for comparation
	buffer2 = calloc(1, PAGE_SIZE);
	if (!buffer2) {
		PR_ERR("allocate 0x%llx bytes buffer2 fail\n", PAGE_SIZE);
		ret = -1;
		goto err_free_buffer;
	}

	fd = open(CSV_ATTESTATION_REPORT_OUT_FILE, O_RDONLY);
	if (fd < 0) {
		PR_ERR("cannot open file %s\n", CSV_ATTESTATION_REPORT_OUT_FILE);
		ret = -1;
		goto err_free_buffer2;
	}

	if (read(fd, buffer, CSV_ATTESTATION_REPORT_SIZE)
	    != CSV_ATTESTATION_REPORT_SIZE) {
		PR_ERR("read report from %s fail\n", CSV_ATTESTATION_REPORT_OUT_FILE);
		ret = -1;
		goto err_close_fd;
	}

	report = (csv_attestation_report_t *)buffer;
	tee_info = (csv_attestation_tee_info_t *)&report->tee_info;

	// verify sm3 hmac
	ptr = (uint32_t *)mnonce_tmp;
	for (i = 0; i < REPORT_MNONCE_SIZE / sizeof(uint32_t); i++)
		ptr[i] = ((uint32_t *)tee_info->mnonce)[i] ^ tee_info->anonce;

	ret = _calc_sm3_hmac(mnonce_tmp, REPORT_MNONCE_SIZE,
			     report->pek_cert, PEK_CERT_SIZE + SN_SIZE + SEALING_KEY_SIZE,
			     buffer2, sizeof(sm3_hash_block_t));
	if (ret) {
		PR_ERR("calc sm3 hmac fail\n");
		ret = -1;
		goto err_close_fd;
	}

	if (memcmp(buffer2, report->sm3_hmac, sizeof(sm3_hash_block_t))) {
		PR_ERR("sm3 hmac invalid in attestation report\n");
		ret = -1;
		goto err_close_fd;
	}

	ptr = (uint32_t *)report->pek_cert;
	for (i = 0; i < sizeof(report->pek_cert) / sizeof(uint32_t); i++)
		ptr[i] ^= tee_info->anonce;
	ptr = (uint32_t *)report->chip_id;
	for (i = 0; i < sizeof(report->chip_id) / sizeof(uint32_t); i++)
		ptr[i] ^= tee_info->anonce;
	pek_cert = (hygon_csv_cert_t *)report->pek_cert;

	// verify cert chain
	if (verify_certchain) {
		ret = verify_hygon_certchain(pek_cert, report->chip_id);
		if (ret != 1) {
			PR_ERR("fail to verify Hygon cert chain\n");
			ret = -1;
			goto err_close_fd;
		} else {
			PR_INFO("Verify Hygon cert chain PASS\n");
		}
	}

	// verify the tee_info in the report
	ret = verify_hygon_signature((const uint8_t *)tee_info,
				     CSV_ATTESTATION_TEE_INFO_SIGN_SIZE,
				     &pek_cert->ecc_pubkey,
				     &tee_info->ecc_sig1);
	if (ret != 1) {
		PR_ERR("Verfiy attestation report FAIL. (tee_info signature is invalid)\n");
		ret = -1;
		goto err_close_fd;
	} else {
		PR_INFO("Verify attestation report PASS\n");
		ret = 0;
	}

err_close_fd:
	close(fd);
err_free_buffer2:
	free(buffer2);
err_free_buffer:
	free(buffer);

	return ret;
}

static int do_attestation_verify_ext(bool verify_certchain, uint32_t flags)
{
	int fd;
	int ret = 0;
	uint8_t *buffer = NULL;
	uint8_t *buffer2 = NULL;
	csv3_attestation_report_ext_t *report_ext = NULL;
	csv3_attestation_tee_info_ext_t *tee_info_ext = NULL;
	hygon_csv_cert_t *pek_cert = NULL;

	if (flags && !(flags & CSV_ATTESTATION_FLAG_REPORT_EXT)) {
		PR_ERR("unknown extension flags 0x%x.\n", flags);
		return -1;
	}

	if (!flags) {
		PR_INFO("extension flags are 0, we will verify the report in legacy format.\n");
		return do_attestation_verify(verify_certchain);
	}

	// allocate 4K size buffer to save report
	buffer = calloc(1, PAGE_SIZE);
	if (!buffer) {
		PR_ERR("allocate 0x%llx bytes buffer fail\n", PAGE_SIZE);
		return -1;
	}

	// allocate 4K size buffer to save temporary data for comparation
	buffer2 = calloc(1, PAGE_SIZE);
	if (!buffer2) {
		PR_ERR("allocate 0x%llx bytes buffer2 fail\n", PAGE_SIZE);
		ret = -1;
		goto err_free_buffer;
	}

	fd = open(CSV_ATTESTATION_REPORT_OUT_FILE, O_RDONLY);
	if (fd < 0) {
		PR_ERR("cannot open file %s\n", CSV_ATTESTATION_REPORT_OUT_FILE);
		ret = -1;
		goto err_free_buffer2;
	}

	if (read(fd, buffer, CSV3_ATTESTATION_REPORT_EXT_SIZE)
	    != CSV3_ATTESTATION_REPORT_EXT_SIZE) {
		PR_ERR("read report from %s fail\n", CSV_ATTESTATION_REPORT_OUT_FILE);
		ret = -1;
		goto err_close_fd;
	}

	report_ext = (csv3_attestation_report_ext_t *)buffer;
	tee_info_ext = (csv3_attestation_tee_info_ext_t *)&report_ext->tee_info;

	// verify sm3 hmac
	ret = _calc_sm3_hmac(tee_info_ext->mnonce, REPORT_MNONCE_SIZE,
			     report_ext->pek_cert, PEK_CERT_SIZE + SN_SIZE + SEALING_KEY_SIZE,
			     buffer2, sizeof(sm3_hash_block_t));
	if (ret) {
		PR_ERR("calc sm3 hmac fail\n");
		ret = -1;
		goto err_close_fd;
	}

	if (memcmp(buffer2, report_ext->sm3_hmac, sizeof(sm3_hash_block_t))) {
		PR_ERR("sm3 hmac invalid in attestation report\n");
		ret = -1;
		goto err_close_fd;
	}

	pek_cert = (hygon_csv_cert_t *)report_ext->pek_cert;

	// verify cert chain
	if (verify_certchain) {
		ret = verify_hygon_certchain(pek_cert, report_ext->chip_id);
		if (ret != 1) {
			PR_ERR("fail to verify Hygon cert chain\n");
			ret = -1;
			goto err_close_fd;
		} else {
			PR_INFO("Verify Hygon cert chain PASS\n");
		}
	}

	// verify the tee_info in the report
	ret = verify_hygon_signature((const uint8_t *)tee_info_ext,
				     CSV3_ATTESTATION_TEE_INFO_EXT_SIGN_SIZE,
				     &pek_cert->ecc_pubkey,
				     &tee_info_ext->ecc_sig1);
	if (ret != 1) {
		PR_ERR("Verfiy attestation report FAIL. (tee_info signature is invalid)\n");
		ret = -1;
		goto err_close_fd;
	} else {
		PR_INFO("Verify attestation report PASS\n");
		ret = 0;
	}

err_close_fd:
	close(fd);
err_free_buffer2:
	free(buffer2);
err_free_buffer:
	free(buffer);

	return ret;
}

static void print_attestation_usage(void)
{
	printf("%-20s %-20s [--help] [--report] [--verify] [--parse]\n"
	       "%-42s[--extension FLAGS] [--certchain]\n\n",
	       g_prog_name, APP_SUBCMD_ATTESTATION, "");
	printf("  --help:      Display this help message.\n");
	printf("  --report:    Request attestation report.\n"
	       "               The report will save to file %s.\n",
	       CSV_ATTESTATION_REPORT_OUT_FILE);
	printf("  --verify:    Verify the attestation report.\n"
	       "               The report will read from file %s.\n",
	       CSV_ATTESTATION_REPORT_OUT_FILE);
	printf("  --parse:     Parse report file %s\n",
	       CSV_ATTESTATION_REPORT_OUT_FILE);
	printf("  --extension: Provide extension flags to indicate how to generate /\n"
	       "               verify / parse the attestation report.\n"
	       "               The FLAGS is in hexadecimal format, such as 0x1.\n");
	printf("  --certchain: Used with --verify option, this option indicates cert-chain\n"
	       "               must be verified when verifying the attestation report.\n");
}

static struct option long_options[] = {
	{ "help", no_argument, 0, 'h' },
	{ "report", no_argument, 0, 'r' },
	{ "verify", no_argument, 0, 'v' },
	{ "parse", no_argument, 0, 'p' },
	{ "extension", required_argument, 0, 'e' },
	{ "certchain", no_argument, 0, 'c' },
	{ 0, 0, 0, 0 },
};

int handle_subcmd_attestation(int argc, char *argv[])
{
	int opt;

	char *endptr = NULL;

	bool is_attestation_report = false;
	bool is_attestation_verify = false;
	bool is_attestation_parse = false;
	bool is_attestation_ext_aware = false;
	bool is_verify_cert_chain = false;

	uint32_t attestation_ext_flags = 0;

	if (argc < 2) {
		print_attestation_usage();
		return -1;
	}

	while ((opt = getopt_long(argc, argv, "hrvpe:c", long_options, NULL)) != -1) {
		switch (opt) {
		case 'h':
			print_attestation_usage();
			return 0;
		case 'r':
			is_attestation_report = true;
			break;
		case 'v':
			is_attestation_verify = true;
			break;
		case 'p':
			is_attestation_parse = true;
			break;
		case 'e':
			is_attestation_ext_aware = true;
			attestation_ext_flags = (uint32_t)strtol(optarg, &endptr, 16);
			if (*endptr != '\0') {
				PR_ERR("Invalid attestation extension flags: %s\n", optarg);
				return -1;
			}
			break;
		case 'c':
			is_verify_cert_chain = true;
			break;
		default:
			PR_ERR("Invalid options !!!\n");
			print_attestation_usage();
			return -1;
		}
	}

	if (is_attestation_report) {
		if (is_attestation_ext_aware)
			return do_attestation_report_ext(attestation_ext_flags);
		else
			return do_attestation_report();
	}

	if (is_attestation_verify) {
		if (is_attestation_ext_aware)
			return do_attestation_verify_ext(is_verify_cert_chain,
							 attestation_ext_flags);
		else
			return do_attestation_verify(is_verify_cert_chain);
	}

	if (is_attestation_parse) {
		if (is_attestation_ext_aware)
			return do_attestation_parse_ext(attestation_ext_flags);
		else
			return do_attestation_parse();
	}

	return 0;
}
